//
//  StatModel.swift
//
//  Created by Ink on 2024-03-15.
//

import Foundation
import GRDB
import SwiftyJSON

public class StatManager: NSObject {
        
    public static var stat_route_source_path: String? = "start"
    
    public static let `default` = StatManager()
    public var uploadLogTime: TimeInterval = 0
    
    public override init() {
        _ = NotificationCenter.default.rx.notification(UIApplication.didEnterBackgroundNotification)
            .subscribe(onNext: {notification in
                StatManager.default.uploadLogTime = 0
                StatManager.uploadLog()
            })
    }
    
    public static var StatOpen = true
    
    static var uploadLogWorkItem: DispatchWorkItem?
    
    static var isStatRequesting = false
    
    public static var statRequestCallBack: ((_ et: [[String: Any?]]?, @escaping (Bool) -> Void) -> Void)?
    
    public static var statModelCallBack: ((StatModel) -> Void)?
    
    var lastStatModels = [StatModel]()
    var hasCommonCount = 0
    
    public static func statError(_ type:StatModel.ErrorType,code: Int? = nil, message: String?, properties:[String:Any?]? = nil) {
        stat(event: .errorInfo, properties: [
            "type": type.rawValue,
            "code": code,
            "message": message,
        ].merging(properties))
    }
    
    public static func stat(event: StatModel.EventType, properties:[String:Any?]? = nil) {
        if properties == nil || properties.isEmpty {
            return
        }
        let log = StatModel(evt_typ: event,kv: properties?.compactMapValues { $0 }.toJsonString())
        statModelCallBack?(log)
        StatDBManager.default.insert(model: log)
    }
    
    @objc public static func uploadLog() {

        guard StatOpen == true, NetWorkManger.isReachable, isStatRequesting != true else {
            return
        }
#if DEBUG
        let stat_time_interval: TimeInterval = 3
#else
        let stat_time_interval: TimeInterval = 15
#endif
        let currentTime = Date().timeIntervalSince1970
        let lastuploadLogTime = StatManager.default.uploadLogTime
        if lastuploadLogTime > 0,
           currentTime - lastuploadLogTime < stat_time_interval {
            return
        }
        StatManager.default.uploadLogTime = currentTime
        
        uploadLogWorkItem?.cancel()
        let workItem = DispatchWorkItem {
            Self.uploadLog()
        }
        uploadLogWorkItem = workItem
        ZAsync(stat_time_interval, workItem)
        
        let item = StatDBManager.default.secrch()
        guard let models = item?.models, models.count > 0 else  {
            return
        }
        
        // 重复数据处理
        let hasCommon = StatManager.default.lastStatModels.contains{ models.contains($0) }
        if hasCommon == true {
            StatManager.default.hasCommonCount += 1
            if StatManager.default.hasCommonCount >= 2 {
                let count = StatDBManager.default.deleteAll()
                if count > 0 {
                    StatManager.default.hasCommonCount = 0
                }
                return
            }
        }
        StatManager.default.lastStatModels = models
        
        let et = models.compactMap({
            var value = $0.toJson()
            value?["kv"] = $0.kv?.convertStringToDictionary()
            return value
        })
        StatManager.isStatRequesting = true
        statRequestCallBack?(et) {success in
            if success {
                let result = StatDBManager.default.deleate(result: item?.result)
                if result == 0 {
                    StatDBManager.default.deleteAll()
                }
            }
            StatManager.isStatRequesting = false
        }
    }
}

public struct StatModel: CodableModel, FetchableRecord, PersistableRecord {
    
    public enum ErrorType: String, Codable {
        case network,player,recharge
    }
    
    public enum EventType: String, Codable {
        case appStart
        case appView
        case exitPage
        case commonPop
        case elementClick
        case elementShow
        case chasingDrama // 追剧/弃剧 action_type 行为类型 数值    1.追剧 0.取消追剧
        case likeChap // 点赞/弃赞 1. 0.取消
        case snapShow // 短剧曝光
        case chapShow // 剧集曝光
        case chapFinish // 剧集完播
        case snapEndPlay // 短剧完播
        case seriesChoosePop
        case popWindow
        case videoPlay
        case errorInfo
        case appFlyerInfo
        
        // 性能监控
        case startMonitor // 启动监控
        case firstFrame //首帧时间监控
//        case playerInitialization //播放器初始化
        case bufferMonitor // 缓冲监控
        case playError // 播放错误监控
        case interfaceMonitor // 接口时长监控
        
        case pullSnapMonitor
        
        case snapPlay // 短剧播放
        case chapWatch // 剧集观看 集完播、集切换、返回上一级
        case chapExit // 剧集退出
        
        case getPermission // 应用权限
        case pushClick
        case pushReceive // push接收
        
        case adStart,adComplete,adProcess
    }
        
    public var evt_typ: EventType
    public var evt_ts: Int = Int(Date().timeIntervalSince1970 * 1000)
    public var kv: String?
}

extension StatModel: Equatable {}

public struct StatDBManager {
    
    public static let `default` = StatDBManager()
        
    public static let dbName = "DB.sqlite"
    public static let tableName = "StatModel"
    
    public static let limit = 50
    public static let DBMaxlimitCount = 100000

    
    public var dbQueue: DatabaseQueue?
    public let idColumn = Column("id")
    public let kvColumn = Column("kv")
    public let evt_typColumn = Column("evt_typ")
    public let evt_tsColumn = Column("evt_ts")
    
    init() {
        let dbPathDir = FileManager.documentPath.append(path: "DB1")
        FileManager.createDirectory(path: dbPathDir)
        let dbPath = dbPathDir.append(path: Self.dbName)
        do {
            self.dbQueue = try DatabaseQueue(path: dbPath)
        } catch let error {
            print("create == \(error)")
        }
        createtable()
    }
    
    public func createtable() {
        do {
            try dbQueue?.write { db in
                try db.create(table: Self.tableName) { t in
                    t.primaryKey(idColumn.name, .integer)
                    t.column(evt_typColumn.name, .text).notNull()
                    t.column(evt_tsColumn.name, .text).notNull()
                    t.column(kvColumn.name, .jsonText)
                }
            }
        } catch let error {
            print("create == \(error)")
        }
        
    }
    
    public func save(models: [StatModel]) {
        if count() > Self.DBMaxlimitCount {
            deleteAll()
        }
        do {
            try dbQueue?.write { db in
                try models.forEach {
                    try $0.save(db)
                }
            }
            
        } catch let error {
            print("insert == \(error)")
        }
    }
    
    public func insert(model: StatModel) {
        let count = count()
        if count > Self.DBMaxlimitCount {
            deleteAll()
        }
        do {
            try dbQueue?.write { db in
                try model.insert(db)
            }
            
        } catch let error {
            print("insert == \(error)")
        }
        StatManager.uploadLog()
    }
    
    //    Player                          // SELECT * FROM player
    //        .filter(nameColumn != nil)  // WHERE (name IS NOT NULL)
    //        .filter(emailColumn != nil) //        AND (email IS NOT NULL)
    //        .order(nameColumn)          // - ignored -
    //        .reversed()                 // - ignored -
    //        .order(scoreColumn)         // ORDER BY score
    //        .limit(20, offset: 40)      // - ignored -
    //        .limit(10)                  // LIMIT 10
    public func secrch() -> (models:[StatModel]?,result:QueryInterfaceRequest<StatModel>)?{
        
        do {
            let result = StatModel.order(idColumn).limit(Self.limit)
            let models = try dbQueue?.read { db in
                try result.fetchAll(db)
            }
            return (models, result)
        } catch let error {
            print("secrch == \(error)")
            return nil
        }
    }
    
    public func count() -> Int {
        do {
            return try dbQueue?.read { db in
                try StatModel.fetchCount(db)
            } ?? 0
        } catch let error {
            print("count == \(error)")
            return 0
        }
    }
    
    @discardableResult
    public func deleate(result: QueryInterfaceRequest<StatModel>?) -> Int {
        var count = 0
        guard let result = result else {
            return count
        }
        do {
            try dbQueue?.write { db in
                count = try result.deleteAll(db)
            }
            return count
        } catch let error {
            print("secrch == \(error)")
            return count
        }
    }
    
    @discardableResult
    public func deleteAll() -> Int{
        var count = 0
        do {
            try dbQueue?.write { db in
                count = try StatModel.deleteAll(db)
            }
            return count
        } catch let error {
            print("secrch == \(error)")
            return count
        }
    }
}
